package com.lap.framework.crypto;

import com.lap.framework.exception.CryptoException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 *
 *
 * <pre>
 * 对称加密：加密和解密使用相同密钥的算法,
 * 常见的加密方式有DES、DES、AES、PBE等加密算法，
 * 这几种算法安全性依次是逐渐增强的。
 * </pre>
 *
 * @author Shuisheng Lao(劳水生)
 * @version 0.0.1
 */
public final class AesUtil {
  private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";
  private static final int TAG_LENGTH_BIT = 128;
  private static final int IV_LENGTH_BYTE = 12;
  private static final int SALT_LENGTH_BYTE = 16;

  private AesUtil() throws IllegalAccessException {
    throw new IllegalAccessException("static class cannot ref");
  }

  /**
   * 对称加密,秘钥必须与解密秘钥一样
   *
   * @param text 加密文件
   * @param key 秘钥
   * @return 加密后的字符串
   */
  public static String encrypt(String text, String key) {
    return encrypt(text.getBytes(StandardCharsets.UTF_8), key);
  }

  /**
   * 对称加密,秘钥必须与解密秘钥一样
   *
   * @param text 加密文件
   * @param key 秘钥
   * @return 加密后的字符串
   */
  public static String encrypt(byte[] text, String key) {
    if (ArrayUtils.isEmpty(text) || StringUtils.isBlank(key)) {
      throw new CryptoException("text/key can not be null");
    }

    byte[] salt = CryptoUtil.getRandomNonce(SALT_LENGTH_BYTE);
    byte[] iv = CryptoUtil.getRandomNonce(IV_LENGTH_BYTE);
    try {
      SecretKey aesKey = CryptoUtil.getAESKeyFromPassword(key.toCharArray(), salt);
      Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
      cipher.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv));

      byte[] cipherText = cipher.doFinal(text);
      byte[] cipherTextWithIvSalt =
          ByteBuffer.allocate(iv.length + salt.length + cipherText.length)
              .put(iv)
              .put(salt)
              .put(cipherText)
              .array();

      return Base64.getEncoder().encodeToString(cipherTextWithIvSalt);
    } catch (Exception e) {
      throw new CryptoException(e);
    }
  }

  /**
   * 对称解密
   *
   * @param text 解密内容
   * @param key 秘钥
   * @return 解密后的字符串
   */
  public static String decrypt(String text, String key) {
    if (StringUtils.isAnyBlank(text, key)) {
      throw new CryptoException("text/key can not be null");
    }

    byte[] decodedText = Base64.getDecoder().decode(text.getBytes(StandardCharsets.UTF_8));
    ByteBuffer byteBuffer = ByteBuffer.wrap(decodedText);

    byte[] iv = new byte[IV_LENGTH_BYTE];
    byteBuffer.get(iv);

    byte[] salt = new byte[SALT_LENGTH_BYTE];
    byteBuffer.get(salt);

    byte[] cipherText = new byte[byteBuffer.remaining()];
    byteBuffer.get(cipherText);
    try {
      SecretKey aesKey = CryptoUtil.getAESKeyFromPassword(key.toCharArray(), salt);
      Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
      cipher.init(Cipher.DECRYPT_MODE, aesKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv));

      byte[] plainText = cipher.doFinal(cipherText);
      return new String(plainText, StandardCharsets.UTF_8);
    } catch (Exception e) {
      throw new CryptoException(e);
    }
  }
}
